Skip to content

perf: add canary patterns to skip statistics during bulk operations#150

Merged
gonzalesedwin1123 merged 2 commits into19.0from
worktree-perf+phase8-canary-patterns
Apr 17, 2026
Merged

perf: add canary patterns to skip statistics during bulk operations#150
gonzalesedwin1123 merged 2 commits into19.0from
worktree-perf+phase8-canary-patterns

Conversation

@kneckinator
Copy link
Copy Markdown
Contributor

@kneckinator kneckinator commented Apr 3, 2026

Summary

  • Add context flags (skip_registrant_statistics, skip_program_statistics) that allow bulk operation callers to suppress expensive computed field recomputation
  • Add refresh_beneficiary_counts() on spp.program and refresh_statistics() on spp.cycle to recompute all statistics once after bulk operations complete
  • Replace bool(rec.program_membership_ids) with SQL query in _compute_has_members to avoid loading full membership recordset
  • Consolidate post-bulk-operation statistics refresh in mark_import_as_done and mark_check_eligibility_as_done

Changes

  • programs.py: Add skip_program_statistics guard to _compute_has_members, replace with SQL query, add refresh_beneficiary_counts()
  • cycle.py: Add refresh_statistics() method
  • registrant.py: Add skip_registrant_statistics guard to 4 computed methods (membership count, entitlements, cycles, in-kind)
  • cycle_manager_base.py: Use cycle.refresh_statistics() in mark_import_as_done
  • eligibility_manager.py: Use program.refresh_beneficiary_counts() in mark_import_as_done
  • test_canary_patterns.py (new): 9 tests covering canary flags, SQL-based has_members, and refresh methods

Context

Phase 8 of 9 in the spp_programs performance optimization effort. Rebased on current 19.0. Version bumped to 19.0.2.0.9.

Test plan

  • ./scripts/test_single_module.sh spp_programs — 615 tests, 0 failures
  • pre-commit run --files <changed_files> — all checks pass

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements bulk membership creation for programs and cycles using raw SQL with ON CONFLICT DO NOTHING to optimize performance and handle duplicate entries. It introduces 'canary' context flags to bypass expensive field recomputations during bulk processes, along with refresh methods to synchronize statistics upon completion. Review feedback highlights a missing refresh method for registrant statistics which could lead to stale data, and questions the redundancy of the cycle statistics refresh method for non-stored fields.

Comment on lines +46 to +47
if self.env.context.get("skip_registrant_statistics"):
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The skip_registrant_statistics context flag allows bypassing the recomputation of store=True fields such as program_membership_count. However, unlike the spp.program and spp.cycle models, no corresponding refresh method is provided for res.partner to recompute these statistics after bulk operations. This will result in stale data remaining in the database if the flag is used or if raw SQL is employed.

Please consider adding a refresh_registrant_statistics() method to the res.partner inheritance and calling it in the appropriate completion handlers (e.g., in mark_import_as_done within eligibility_manager.py).

Comment on lines +278 to +286
def refresh_statistics(self):
"""Refresh all cycle statistics after bulk operations.

Call this after raw SQL inserts that bypass ORM dependency tracking
(e.g. bulk_create_memberships with skip_duplicates=True).
"""
self._compute_members_count()
self._compute_entitlements_count()
self._compute_total_entitlements_count()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The refresh_statistics method appears to be redundant in its current implementation. The fields it attempts to refresh (members_count, entitlements_count, and total_entitlements_count) are all store=False and do not implement the "canary" skip logic found in other models.

Since these fields are computed on-demand and the underlying relation caches are correctly invalidated in the managers (e.g., via cycle.invalidate_recordset(['cycle_membership_ids'])), they will naturally reflect the correct values upon the next access without an explicit refresh call. If the intention was to optimize these fields for bulk operations, they should be made store=True and implement the skip logic, similar to has_members in spp.program.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 3, 2026

Codecov Report

❌ Patch coverage is 81.48148% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.36%. Comparing base (26b9060) to head (8c49d83).
⚠️ Report is 3 commits behind head on 19.0.

Files with missing lines Patch % Lines
spp_programs/models/programs.py 76.92% 3 Missing ⚠️
spp_programs/models/managers/cycle_manager_base.py 0.00% 1 Missing ⚠️
...pp_programs/models/managers/eligibility_manager.py 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             19.0     #150   +/-   ##
=======================================
  Coverage   71.35%   71.36%           
=======================================
  Files         932      932           
  Lines       54769    54794   +25     
=======================================
+ Hits        39080    39103   +23     
- Misses      15689    15691    +2     
Flag Coverage Δ
spp_api_v2 80.10% <ø> (ø)
spp_api_v2_change_request 66.85% <ø> (ø)
spp_api_v2_cycles 71.12% <ø> (ø)
spp_api_v2_data 64.41% <ø> (ø)
spp_api_v2_entitlements 70.19% <ø> (ø)
spp_api_v2_gis 71.52% <ø> (ø)
spp_api_v2_products 66.27% <ø> (ø)
spp_api_v2_service_points 70.94% <ø> (ø)
spp_api_v2_simulation 71.12% <ø> (ø)
spp_api_v2_vocabulary 57.26% <ø> (ø)
spp_audit 72.60% <ø> (+0.06%) ⬆️
spp_base_common 90.26% <ø> (ø)
spp_case_entitlements 97.61% <ø> (ø)
spp_case_programs 97.14% <ø> (ø)
spp_cel_event 85.11% <ø> (ø)
spp_claim_169 58.11% <ø> (ø)
spp_dci_client_dr 55.87% <ø> (ø)
spp_dci_client_ibr 60.17% <ø> (ø)
spp_programs 63.59% <81.48%> (+0.09%) ⬆️
spp_security 66.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_programs/__manifest__.py 0.00% <ø> (ø)
spp_programs/models/cycle.py 65.27% <100.00%> (+0.24%) ⬆️
spp_programs/models/registrant.py 83.87% <100.00%> (+1.51%) ⬆️
spp_programs/models/managers/cycle_manager_base.py 66.36% <0.00%> (ø)
...pp_programs/models/managers/eligibility_manager.py 75.60% <0.00%> (+0.60%) ⬆️
spp_programs/models/programs.py 87.53% <76.92%> (-0.43%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

kneckinator and others added 2 commits April 17, 2026 10:39
Add context flags (skip_registrant_statistics, skip_program_statistics)
that allow bulk operation callers to suppress expensive computed field
recomputation. Add refresh_beneficiary_counts() on spp.program and
refresh_statistics() on spp.cycle to recompute once at completion.

Also replace bool(rec.program_membership_ids) with SQL EXISTS in
_compute_has_members to avoid loading the full membership recordset.
@gonzalesedwin1123 gonzalesedwin1123 force-pushed the worktree-perf+phase8-canary-patterns branch from 1c16e0c to 8c49d83 Compare April 17, 2026 02:58
@gonzalesedwin1123 gonzalesedwin1123 merged commit 1f4203c into 19.0 Apr 17, 2026
35 checks passed
@gonzalesedwin1123 gonzalesedwin1123 deleted the worktree-perf+phase8-canary-patterns branch April 17, 2026 03:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants